#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "buf.h"


// Simple logging that allows concurrent FS system calls.
// 简单的日志系统，允许并发的文件系统系统调用。
//
// A log transaction contains the updates of multiple FS system
// calls. The logging system only commits when there are
// no FS system calls active. Thus there is never
// any reasoning required about whether a commit might
// write an uncommitted system call's updates to disk.
// 一个日志事务包含多个文件系统系统调用的更新。
// 日志系统只在没有活跃的文件系统系统调用时提交。
// 因此，无需考虑提交是否会将未提交的系统调用的更新写入磁盘。
//
// A system call should call begin_op()/end_op() to mark
// its start and end. Usually begin_op() just increments
// the count of in-progress FS system calls and returns.
// But if it thinks the log is close to running out, it
// sleeps until the last outstanding end_op() commits.
// 一个系统调用应该调用 begin_op()/end_op() 来标记其开始和结束。
// 通常 begin_op() 只会增加正在进行的 FS 系统调用的计数并返回。
// 但如果它认为日志快要用尽，它会休眠直到最后一个未完成的 end_op() 提交。
//
// The log is a physical re-do log containing disk blocks.
// 日志是一个包含磁盘块的物理重做日志。
// The on-disk log format:
// 磁盘上的日志格式：
//   header block, containing block #s for block A, B, C, ...
//   头部块，包含块 A, B, C, ... 的块号
//   block A
//   块 A
//   block B
//   块 B
//   block C
//   块 C
//   ...
//   ...
// Log appends are synchronous.
// 日志追加是同步的。

// Contents of the header block, used for both the on-disk header block
// and to keep track in memory of logged block# before commit.
// 头部块的内容，用于磁盘上的头部块和在提交前在内存中跟踪已记录的块号。
struct logheader {
  int n; // 日志中块的数量
  int block[LOGSIZE]; // 记录的块号数组
};

struct log {
  struct spinlock lock; // 日志锁
  int start; // 日志区域在磁盘上的起始块号
  int size; // 日志区域的大小（块数）
  int outstanding; // 正在执行的文件系统系统调用数量
  int committing;  // 是否正在提交 (in commit(), please wait.)
  int dev; // 设备号
  struct logheader lh; // 内存中的日志头部
};
struct log log; // 日志结构体实例

static void recover_from_log(void); // 从日志中恢复
static void commit(); // 提交事务

void
initlog(int dev)
{
  // 检查日志头部大小是否超过块大小
  if (sizeof(struct logheader) >= BSIZE)
    panic("initlog: too big logheader");

  struct superblock sb;
  initlock(&log.lock, "log"); // 初始化日志锁
  readsb(dev, &sb); // 读取超级块
  log.start = sb.logstart; // 设置日志起始块号
  log.size = sb.nlog; // 设置日志大小
  log.dev = dev; // 设置设备号
  recover_from_log(); // 从日志中恢复
}

// Copy committed blocks from log to their home location
// 将已提交的块从日志区域复制到它们的原始位置
// 修改 install_trans，增加一个参数 from_log
static void
install_trans(int from_log)
{
  int tail;

  // 遍历日志头部中记录的块
  for (tail = 0; tail < log.lh.n; tail++) {
    struct buf *dbuf; // 目标块的缓冲区
    if(from_log){
      // 崩溃恢复，从日志区读
      struct buf *lbuf = bread(log.dev, log.start+tail+1); // 读取日志块
      dbuf = bread(log.dev, log.lh.block[tail]); // 读取目标块
      memmove(dbuf->data, lbuf->data, BSIZE);  // 将日志块数据复制到目标块
      brelse(lbuf); // 释放日志块缓冲区
    }else{
      // 正常 commit，直接用缓存
      dbuf = bread(log.dev, log.lh.block[tail]); // 读取目标块（此时缓存中已是最新数据）
      // 缓存中已是最新数据，无需拷贝
    }
    bwrite(dbuf);  // 将目标块写入磁盘
    brelse(dbuf); // 释放目标块缓冲区
  }
}

// Read the log header from disk into the in-memory log header
// 从磁盘读取日志头部到内存中的日志头部
static void
read_head(void)
{
  struct buf *buf = bread(log.dev, log.start); // 读取日志头部块
  struct logheader *lh = (struct logheader *) (buf->data); // 获取日志头部指针
  int i;
  log.lh.n = lh->n; // 更新内存中日志头部的块数量
  for (i = 0; i < log.lh.n; i++) {
    log.lh.block[i] = lh->block[i]; // 更新内存中日志头部的块号数组
  }
  brelse(buf); // 释放缓冲区
}

// Write in-memory log header to disk.
// 将内存中的日志头部写入磁盘。
// This is the true point at which the
// current transaction commits.
// 这是当前事务真正提交的时间点。
static void
write_head(void)
{
  struct buf *buf = bread(log.dev, log.start); // 读取日志头部块
  struct logheader *hb = (struct logheader *) (buf->data); // 获取日志头部指针
  int i;
  hb->n = log.lh.n; // 将内存中日志头部的块数量写入缓冲区
  for (i = 0; i < log.lh.n; i++) {
    hb->block[i] = log.lh.block[i]; // 将内存中日志头部的块号数组写入缓冲区
  }
  bwrite(buf); // 将缓冲区写入磁盘
  brelse(buf); // 释放缓冲区
}

static void
recover_from_log(void)
{
  read_head(); // 读取日志头部
  cprintf("recovery: n=%d\n", log.lh.n); // 打印恢复信息
  install_trans(1); // 崩溃恢复，从日志区读，将日志中的数据安装到原始位置
  log.lh.n = 0; // 清空内存中日志头部的块数量
  write_head(); // 将清空的日志头部写入磁盘，标记恢复完成
}

// called at the start of each FS system call.
// 在每个文件系统系统调用开始时调用。
void
begin_op(void)
{
  acquire(&log.lock); // 获取日志锁
  while(1){
    if(log.committing){
      // 如果正在提交，则休眠等待
      sleep(&log, &log.lock);
    } else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){
      // 如果当前操作可能耗尽日志空间，则休眠等待提交
      // this op might exhaust log space; wait for commit.
      sleep(&log, &log.lock);
    } else {
      log.outstanding += 1; // 增加正在进行的系统调用计数
      release(&log.lock); // 释放日志锁
      break; // 退出循环
    }
  }
}

// called at the end of each FS system call.
// 在每个文件系统系统调用结束时调用。
// commits if this was the last outstanding operation.
// 如果这是最后一个未完成的操作，则提交。
void
end_op(void)
{
  int do_commit = 0; // 是否需要提交的标志

  acquire(&log.lock); // 获取日志锁
  log.outstanding -= 1; // 减少正在进行的系统调用计数
  if(log.committing)
    panic("log.committing"); // 如果正在提交时调用 end_op，则出错
  if(log.outstanding == 0){
    // 如果没有未完成的操作，则设置提交标志并标记正在提交
    do_commit = 1;
    log.committing = 1;
  } else {
    // begin_op() may be waiting for log space,
    // and decrementing log.outstanding has decreased
    // the amount of reserved space.
    // begin_op() 可能正在等待日志空间，
    // 并且减少 log.outstanding 已经减少了保留空间的数量。
    wakeup(&log); // 唤醒等待日志空间的进程
  }
  release(&log.lock); // 释放日志锁

  if(do_commit){
    // call commit w/o holding locks, since not allowed
    // to sleep with locks.
    // 在不持有锁的情况下调用 commit，因为不允许在持有锁时休眠。
    commit(); // 执行提交
    acquire(&log.lock); // 重新获取日志锁
    log.committing = 0; // 清除正在提交的标志
    wakeup(&log); // 唤醒等待提交完成的进程
    release(&log.lock); // 释放日志锁
  }
}

// Copy modified blocks from cache to log.
// 将修改过的块从缓存复制到日志区域。
static void
write_log(void)
{
  int tail;

  // 遍历日志头部中记录的块
  for (tail = 0; tail < log.lh.n; tail++) {
    struct buf *to = bread(log.dev, log.start+tail+1); // 读取日志块的缓冲区
    struct buf *from = bread(log.dev, log.lh.block[tail]); // 读取缓存中原始块的缓冲区
    memmove(to->data, from->data, BSIZE); // 将缓存中的数据复制到日志块缓冲区
    bwrite(to);  // 将日志块写入磁盘
    brelse(from); // 释放原始块缓冲区
    brelse(to); // 释放日志块缓冲区
  }
}


static void
commit()
{
  if (log.lh.n > 0) {
    write_log();     // Write modified blocks from cache to log
    write_head();    // Write header to disk -- the real commit
    install_trans(0); // 正常 commit，直接用缓存，将日志中的数据安装到原始位置
    log.lh.n = 0; // 清空内存中日志头部的块数量
    write_head();    // Erase the transaction from the log
  }
}

// Caller has modified b->data and is done with the buffer.
// 调用者已经修改了 b->data 并完成了对缓冲区的操作。
// Record the block number and pin in the cache with B_DIRTY.
// 记录块号并在缓存中用 B_DIRTY 标记固定。
// commit()/write_log() will do the disk write.
// commit()/write_log() 将执行磁盘写入。
//
// log_write() replaces bwrite(); a typical use is:
// log_write() 替换了 bwrite()；典型的用法是：
//   bp = bread(...) // 读取块
//   modify bp->data[] // 修改数据
//   log_write(bp) // 记录日志
//   brelse(bp) // 释放缓冲区
void
log_write(struct buf *b)
{
  int i;

  // 检查事务是否过大
  if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1)
    panic("too big a transaction");
  // 检查是否在事务外部调用 log_write
  if (log.outstanding < 1)
    panic("log_write outside of trans");

  acquire(&log.lock); // 获取日志锁
  // 检查块是否已经在日志中记录 (log absorbtion)
  for (i = 0; i < log.lh.n; i++) {
    if (log.lh.block[i] == b->blockno)
      break;
  }
  log.lh.block[i] = b->blockno; // 记录块号
  if (i == log.lh.n)
    log.lh.n++; // 如果是新块，增加日志中块的数量
  b->flags |= B_DIRTY; // 标记缓冲区为脏，防止被逐出缓存
  release(&log.lock); // 释放日志锁
}
